package main

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"unicode"
)

const (
	KEYWORD int = iota
	IDENTIFIER
	CONSTANT
	OPEARTOR
	DOT
)

var (
	// 要分析的字符串
	content []rune
	// 该字符串的长度
	length int
	// 游标
	curIndex int
	// 是否完成
	isComplete bool
	// 当前字符
	peek rune
)

// 设置字符串
func setContent(str string) bool {
	if len(str) == 0 {
		return false
	}
	content = []rune(str)
	length = len(content)
	curIndex = 0
	return true
}

// 读取一个字符
func readChar() {
	if curIndex < length {
		peek = content[curIndex]
		curIndex++
	} else {
		isComplete = true
	}
}

// 读取一个字符并判定
func isChar(c rune) bool {
	readChar()
	if peek != c {
		return false
	} else {
		return true
	}
}

// 是否匹配算符
func isMatchOperatePrefix(c rune) bool {
	s := "&|=!<>+-*/"
	return strings.ContainsRune(s, c)
}

// 是否为关键字
func isKeyWord(str string) bool {
	s := [...]string{"auto", "break", "case",
		"char", "const", "continue", "default", "do", "double", "else",
		"enum", "extern", "false", "float", "for", "goto", "if",
		"into", "int", "long", "register", "return", "short", "signed",
		"static", "sizeof", "struct", "switch", "true", "typeof",
		"union", "unsigned", "void", "voliatile", "while"}
	for i := 0; i < len(s); i++ {
		if str == s[i] {
			return true
		}
	}
	return false
}

// 是否为基本字符
func isDot(c rune) bool {
	s := "@#%^();:?,\"'{}[]."
	return strings.ContainsRune(s, c)
}

// 分析
func Analyse(str string) ([]string, *symbolTable, error) {
	result := make([]string, 0)
	sTable := NewSymbolTable()
	if !setContent(str) {
		return result, sTable, errors.New("源程序为空！")
	}
	isRead := true
	for {
		if isComplete {
			break
		}
		if isRead {
			readChar()
		}
		isRead = true
		if peek == ' ' || peek == '\t' || peek == '\n' {
			continue
		}
		if isMatchOperatePrefix(peek) {
			switch peek {
			case '&':
				if isChar('&') {
					result = append(result, fmt.Sprintf("(%d, &&)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, &)", OPEARTOR))
				}
			case '|':
				if isChar('|') {
					result = append(result, fmt.Sprintf("(%d, ||)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, |)", OPEARTOR))
				}
			case '!':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, !=)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, !)", OPEARTOR))
				}
			case '<':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, <=)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, <)", OPEARTOR))
				}
			case '>':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, >=)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, >)", OPEARTOR))
				}
			case '+':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, +=)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, +)", OPEARTOR))
				}
			case '-':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, -=)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, -)", OPEARTOR))
				}
			case '*':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, *=)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, *)", OPEARTOR))
				}
			case '/':
				readChar()
				if peek == '=' {
					result = append(result, fmt.Sprintf("(%d, /=)", OPEARTOR))
				} else if peek == '/' {
					for {
						readChar()
						if peek == '\n' {
							break
						}
					}
					isRead = false
				} else {
					result = append(result, fmt.Sprintf("(%d, /)", OPEARTOR))
				}
			case '=':
				if isChar('=') {
					result = append(result, fmt.Sprintf("(%d, ==)", OPEARTOR))
				} else {
					result = append(result, fmt.Sprintf("(%d, =)", OPEARTOR))
				}
			}
		}
		if unicode.IsLetter(peek) || peek == '_' {
			var temp string
			for {
				temp += string(peek)
				readChar()
				if !unicode.IsLetter(peek) && peek != '_' && !unicode.IsDigit(peek) {
					break
				}
			}
			if isKeyWord(temp) {
				result = append(result, fmt.Sprintf("(%d, %s)", KEYWORD, temp))
			} else {
				result = append(result, fmt.Sprintf("(%d, %s)", IDENTIFIER, temp))
				sTable.addItem(fmt.Sprintf("id %s", temp))
			}
		}
		if unicode.IsDigit(peek) {
			v := 0
			for {
				num, _ := strconv.Atoi(string(peek))
				v = 10*v + num
				readChar()
				if !unicode.IsDigit(peek) {
					break
				}
			}
			if peek != '.' {
				result = append(result, fmt.Sprintf("(%d, %d)", CONSTANT, v))
				sTable.addItem(fmt.Sprintf("num %d", v))
			} else {
				x := float64(v)
				d := float64(10)
				for {
					readChar()
					if !unicode.IsDigit(peek) {
						break
					}
					num, _ := strconv.Atoi(string(peek))
					x = x + float64(num)/d
					d = d * 10
				}
				result = append(result, fmt.Sprintf("(%d, %f)", CONSTANT, x))
				sTable.addItem(fmt.Sprintf("num %f", x))
			}
			isRead = false
		}
		if isDot(peek) {
			result = append(result, fmt.Sprintf("(%d, %c)", DOT, peek))
			isRead = true
		}
	}
	return result, sTable, nil
}
